#include <iostream>

// glad.h 要放在glfw3.h之前
#include <glad/glad.h>
#include "GLFW/glfw3.h"
#include "imgui/imgui.h"
#include "imgui/imgui_impl_glfw.h"
#include "imgui/imgui_impl_opengl3.h"
#include <cassert>

#define WIN_WINDTH 600
#define WIN_HEIGHT 600

GLuint program;
// 三个缓冲对象
GLuint vao;
GLuint vbo[2];
GLuint ebo;

// 定义当前绘制模式
static GLuint currentMode = GL_POINTS;//默认点模式绘制

/**
 * 初始化ImGui
 */
void initImGui(GLFWwindow* pWindow);
/**
 * 初始化物体
 */
void initObject();
/**
 * 通过顶点着色器和片元着色器获得Program
 * @param vsSource 顶点着色器源码
 * @param fsSource 片元着色器源码
 */
GLuint getProgramFromShaderSource(const char* vsSource, const char* fsSource);
/**
 * 编译着色器
 * @param src 着色器源码
 * @param type GL_VERTEX_SHADER,GL_FRAGMENT_SHADER,GL_GEOMETRY_SHADER
 */
GLuint compileShader(const char* src, GLenum type);
/**
 * 渲染场景
 */
void renderScene();
/**
 * 渲染UI
 */
void renderUI();

void renderMyUi();

int main(){
    std::cout<<"01_render_simple_graphic!"<<std::endl;

    // 1.初始化
    glfwInit();
    //主版本 3.3.5，所以MAJOR=3,MINOR=3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    //次版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    //使用核心模式
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // 2.创建窗口 https://www.glfw.org/docs/latest/window_guide.html
    GLFWwindow* pWindow = glfwCreateWindow(WIN_WINDTH, WIN_HEIGHT, "hello opengl中文!", nullptr, nullptr);
    //上下文设置
    glfwMakeContextCurrent(pWindow);

    // 3.glad管理函数指针
    bool bLoadSuc = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
    if(!bLoadSuc){
        std::cout << "Failed glad load glProcAddress!" << std::endl;
        return -1;
    }

    // 初始化ImGui
    initImGui(pWindow);

    // 初始化Object
    initObject();

    // 4.主循环
    while (!glfwWindowShouldClose(pWindow)) {
        // 背景色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // 清空缓冲（不执行的话，会保留上一帧的像素）
        glClear(GL_COLOR_BUFFER_BIT);

        //渲染场景
        renderScene();

        // 注意场景和UI的渲染顺序，谁在后面谁被遮挡
        renderUI();

        //交换缓存
        glfwSwapBuffers(pWindow);
        //事件处理
        glfwPollEvents();
    }

    // 5.资源回收
    glfwTerminate();
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

    return 0;
}

/**
 * 初始化ImGui
 */
void initImGui(GLFWwindow* pWindow) {
    // 检查版本
    IMGUI_CHECKVERSION();
    // 上下文
    ImGui::CreateContext();
    // 设置黑色主题
    ImGui::StyleColorsDark();
    // 嵌入住glfw上下文中
    ImGui_ImplGlfw_InitForOpenGL(pWindow, true);
    // OpenGL版本号3.3
    ImGui_ImplOpenGL3_Init("#version 330");

    ImGuiIO& io = ImGui::GetIO();
    (void) io;
    // 字体设置
    io.Fonts->AddFontFromFileTTF("resource/fonts/Cousine-Regular.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesDefault());
    io.Fonts->AddFontFromFileTTF("resource/fonts/DroidSans.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesDefault());
    io.Fonts->AddFontFromFileTTF("resource/fonts/Karla-Regular.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesDefault());
    io.Fonts->AddFontFromFileTTF("resource/fonts/ProggyClean.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesDefault());
    io.Fonts->AddFontFromFileTTF("resource/fonts/Roboto-Medium.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesDefault());
}

/**
 * 渲染场景
 */
void renderScene() {
    // 6. draw
    // 应用着色器程序
    glUseProgram(program);
    // 绑定顶点数组对象 VAO
    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glPointSize(5.0f);

    //绘制
    // GLenum mode 模式 GL_POINTS点、GL_LINES 线、GL_LINE_LOOP线圈（最后一个顶点和第一个顶点有一条连线）、GL_LINE_STRIP 线段、GL_TRIANGLES三角形...
    // GLint first 起始索引，三个顶点都要绘制，所以起始是0
    // GLsizei count 绘制总数，三个顶点，所以是3
    //glDrawArrays(GL_POINTS, 0, 3);
    //glDrawElements(GL_POINTS, 3, GL_UNSIGNED_INT, 0);
    //glDrawArrays(currentMode, 0, 6);
    glDrawElements(currentMode, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

/**
 * 渲染UI
 */
void renderUI() {
    // 1. ImGui渲染操作
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // 2. UI准备
    bool bShowDemoWindow = true;
    // 显示ImGui自带的demo window
    ImGui::ShowDemoWindow(&bShowDemoWindow);

    renderMyUi();

    // 3. 渲染
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

/**
 * 自定义窗口
 */
void renderMyUi() {
    // 列出所有的绘制模式，因为ImGui的选择空间Combo只支持字符串作为预览值，所以这里额外顶一个数组。
    const GLuint modelItems[] = {GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN};
    // 展示选择框的文字，比如 GL_POINTS可以写作“点”（注意，这里中文可能会乱码，需要选择正确的字体格式才可以，后续会写）
    const char* previewText[] = {"GL_POINTS", "GL_LINES", "GL_LINE_STRIP", "GL_LINE_LOOP", "GL_TRIANGLES", "GL_TRIANGLE_STRIP", "GL_TRIANGLE_FAN" };
    static int item_current_idx = currentMode;
    const char* combo_preview_value = previewText[item_current_idx];
    if (ImGui::BeginCombo("Draw Mode", combo_preview_value))
    {
        // 循环定义选择框选项
        for (int n = 0; n < IM_ARRAYSIZE(previewText); n++)
        {
            const bool is_selected = (item_current_idx == n);
            if (ImGui::Selectable(previewText[n], is_selected))
                item_current_idx = n;

            if (is_selected)
                ImGui::SetItemDefaultFocus();
                // 赋值
                currentMode = modelItems[item_current_idx];
        }
        ImGui::EndCombo();
    }
}


/**
 * 初始化场景中物体
 */
void initObject() {
    // 1. 初始化着色器， 目前添加两个，一个顶点着色器，一个是片元着色器
    const char* vsSource = "#version 330 core\n"
                           "layout (location = 0) in vec2 a_position;\n" // 位置
                           "layout (location = 1) in vec4 a_color;\n" // 颜色 ...
                           "out vec4 v_color;\n"
                           "void main(){\n"
                           "  gl_Position = vec4(a_position, 0, 1);\n"
                           "  v_color = a_color;\n"
                           "}";
    const char* fsSource = "#version 330 core\n"
                           "in vec4 v_color;\n"
                           "out vec4 FragColor;\n"
                           "void main(){\n"
                           "    FragColor = v_color;\n"
                           "}";

    program = getProgramFromShaderSource(vsSource, fsSource);

    //2. 准备顶点属性数据； (1) 位置、 (2) 颜色
    // 如何表示一个顶点数据? 在计算机层面如何存储。 Point(x, y)， 笛卡尔坐标系方式，来确认出一个点的位置。存储使用数据存储
    float vertexPos[] = {
            0.0f, 0.5f, //第一个顶点
            0.0f, 0.0f, //第二个顶点
            0.5f, 0.0f, //第三个顶点
//
//            0.0f, 0.5f, //第一个顶点
//            0.5f, 0.0f, //第二个顶点
//            0.5f, 0.5f, //第三个顶点
            0.5f, 0.5f, //第四个顶点
    };

    // 如何表示颜色数据：RGB色彩空间、HSV色彩空间... 这里采用RGB，然后加一个A表示透明度
    float vertexColor[] = {
            1.0f, 0.0f, 0.0f, 1.0f, //红色
            0.0f, 1.0f, 0.0f, 1.0f, //绿色
            1.0f, 1.0f, 0.0f, 1.0f, //黄色
//
//            1.0f, 0.0f, 0.0f, 1.0f, //红色
//            1.0f, 1.0f, 0.0f, 1.0f, //黄色
            0.0f, 1.0f, 1.0f, 1.0f, // 第四个顶点的颜色
    };

    int indices[] = {
            0, 1, 2,
            0, 2, 3
    };


    //3. 创建VAO,VBO,EBO 三个缓冲
    glGenVertexArrays(1, &vao);
    glGenBuffers(2, vbo);
    glGenBuffers(1, &ebo);

    //4. 绑定并设置数据
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPos), vertexPos, GL_STATIC_DRAW);
    GLuint  loc = glGetAttribLocation(program, "a_position");
    glEnableVertexAttribArray(loc);
    // GLuint index 属性的location
    // GLint size 构成每个属性的分量个数：比如我们定的坐标只有x,y两个分量，这里就是2。到了三维阶段，我们需要定义x,y,z，值就是3
    // GLenum type 构成每个属性的分量数据类型，我们定义了float数组，对应OpenGL中则是GL_FLOAT枚举
    // GLboolean normalized 是否标准化，如果为true, 向量的标准化。详细后边写向量的地方会涉及这个点
    // GLsizei stride 步长，加入我们在一个数组中放很多属性，步长就是顶点属性组之间的间隔。 learn-opengl.io网站描述的比较详细。但因为我们把每个属性都分开VBO了，这里都是0
    // const void *pointer 起始偏移值
    glVertexAttribPointer(loc, 2, GL_FLOAT, false, 0, (void*) 0);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexColor), vertexColor, GL_STATIC_DRAW);
    loc = glGetAttribLocation(program, "a_color");
    glEnableVertexAttribArray(loc);
    glVertexAttribPointer(loc, 4, GL_FLOAT, false, 0, (void*) 0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 5.解除绑定
    glBindVertexArray(0);
}

/**
 * 通过顶点着色器和片元着色器获得Program
 * @param vsSource 顶点着色器源码
 * @param fsSource 片元着色器源码
 */
GLuint getProgramFromShaderSource(const char* vsSource, const char* fsSource) {
    GLuint vs = compileShader(vsSource, GL_VERTEX_SHADER);
    GLuint fs = compileShader(fsSource, GL_FRAGMENT_SHADER);

    GLuint program = glCreateProgram();
    int bLinkSuc;
    int logLen = 512;
    char infoLog[512];

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &bLinkSuc);

    if(!bLinkSuc){
        glGetProgramInfoLog(program, logLen, NULL, infoLog);
        std::cout << infoLog << std::endl;
        assert(0);
    }

    glDeleteShader(vs);
    glDeleteShader(fs);
    return program;
}

/**
 * 编译着色器
 * @param src 着色器源码
 * @param type GL_VERTEX_SHADER,GL_FRAGMENT_SHADER,GL_GEOMETRY_SHADER
 */
GLuint compileShader(const char* src, GLenum type) {
    int bCompileSuc;
    int logLen = 512;
    char infoLog[512];

    GLuint vs = glCreateShader(type);
    glShaderSource(vs, 1, &src, NULL);
    glCompileShader(vs);
    glGetShaderiv(vs, GL_COMPILE_STATUS, &bCompileSuc);
    if(!bCompileSuc){
        glGetShaderInfoLog(vs, logLen, NULL, infoLog);
        std::cout << infoLog << std::endl;
        assert(0);
    }
    return vs;
}
